/*
 * Copyright 2021-2022 the original author and authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package cn.edu.tjnu.tutor.system.service.impl;

import cn.edu.tjnu.tutor.common.constant.RoleConst;
import cn.edu.tjnu.tutor.common.util.SqlUtils;
import cn.edu.tjnu.tutor.system.domain.entity.Group;
import cn.edu.tjnu.tutor.system.domain.extra.UserGroup;
import cn.edu.tjnu.tutor.system.domain.view.GroupApplyVO;
import cn.edu.tjnu.tutor.system.domain.view.GroupReplyVO;
import cn.edu.tjnu.tutor.system.domain.view.GroupVO;
import cn.edu.tjnu.tutor.system.domain.view.UserVO;
import cn.edu.tjnu.tutor.system.mapper.GroupMapper;
import cn.edu.tjnu.tutor.system.mapper.RoleMapper;
import cn.edu.tjnu.tutor.system.mapper.UserGroupMapper;
import cn.edu.tjnu.tutor.system.service.GroupService;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.baomidou.mybatisplus.extension.toolkit.ChainWrappers;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * 导师小组信息服务层实现。
 *
 * @author 王帅
 * @since 2.0
 */
@Service
@RequiredArgsConstructor
public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements GroupService {

    private final RoleMapper roleMapper;
    private final UserGroupMapper userGroupMapper;

    @Override
    public <E extends IPage<GroupVO>> E pageVO(Integer userId, E page) {
        return baseMapper.selectPageVO(userId, page);
    }

    @Override
    public <E extends IPage<UserVO>> E pageUserVO(Integer groupId, E page) {
        return baseMapper.selectPageUserVO(groupId, page);
    }

    @Override
    public <E extends IPage<GroupApplyVO>> E pageApplyVO(Integer userId, E page) {
        return baseMapper.selectPageApplyVO(userId, page);
    }

    @Override
    public <E extends IPage<GroupReplyVO>> E pageReplyVO(Integer userId, E page) {
        return baseMapper.selectPageReplyVO(userId, page);
    }

    @Override
    public boolean notIn(Integer teacherId, Integer studentId) {
        return !SqlUtils.toBool(baseMapper.selectUserCount(teacherId, studentId));
    }

    @Override
    public boolean canApply(Integer userId, Integer groupId) {
        // UserGroup 不存在记录 => TRUE 可以申请过小组
        return !refQueryChain()
                .select(UserGroup::getType)
                .eq(UserGroup::getUserId, userId)
                .eq(UserGroup::getGroupId, groupId)
                .exists();
    }

    @Override
    public boolean isNotApplied(Integer userId, Integer groupId) {
        // UserGroup 不存在记录 => !FALSE 未申请过小组
        // UserGroup getType == 1 => !TRUE 申请过小组
        return !refQueryChain()
                .select(UserGroup::getType)
                .eq(UserGroup::getUserId, userId)
                .eq(UserGroup::getGroupId, groupId)
                .oneOpt()
                .map(e -> e.getType() == 1)
                .orElse(Boolean.FALSE);
    }

    @Override
    public boolean apply(UserGroup entity) {
        return SqlUtils.toBool(userGroupMapper.insert(entity));
    }

    @Override
    public boolean updateApply(UserGroup entity) {
        return refUpdateChain()
                .eq(UserGroup::getUserId, entity.getUserId())
                .eq(UserGroup::getGroupId, entity.getGroupId())
                .eq(UserGroup::getType, 1)
                .update(entity);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean agree(Integer userId, Integer groupId) {
        Group group = lambdaQuery()
                .select(Group::getType, Group::getStock)
                .eq(Group::getUserId, userId)
                .eq(Group::getGroupId, groupId)
                .one();

        Integer stock = group.getStock();

        if (stock <= 0) {
            return false;
        }

        if (PRACTICAL.equals(group.getType())) {
            roleMapper.bindRole(userId, RoleConst.ROLE_INTERN);
        }

        lambdaUpdate()
                .set(Group::getStock, stock - 1)
                .eq(Group::getUserId, userId)
                .eq(Group::getGroupId, groupId)
                .update();

        refUpdateChain()
                .eq(UserGroup::getUserId, userId)
                .eq(UserGroup::getGroupId, groupId)
                .set(UserGroup::getType, 1)
                .update();

        return true;
    }

    @Override
    public boolean reject(Integer userId, Integer groupId) {
        Wrapper<UserGroup> wrapper = refQueryChain()
                .eq(UserGroup::getUserId, userId)
                .eq(UserGroup::getGroupId, groupId);
        return SqlUtils.toBool(userGroupMapper.delete(wrapper));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean bindCaptain(Integer userId, Integer groupId) {
        boolean updated = lambdaUpdate()
                .eq(Group::getUserId, userId)
                .eq(Group::getGroupId, groupId)
                .set(Group::getCaptainId, userId)
                .update();
        if (!updated) {
            return false;
        }
        return SqlUtils.toBool(roleMapper.bindRole(userId, RoleConst.ROLE_CAPTAIN));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean dumpCaptain(Integer userId, Integer groupId) {
        boolean updated = lambdaUpdate()
                .eq(Group::getUserId, userId)
                .eq(Group::getGroupId, groupId)
                .set(Group::getCaptainId, 0)
                .update();
        if (!updated) {
            return false;
        }
        return SqlUtils.toBool(roleMapper.dumpRole(userId, RoleConst.ROLE_CAPTAIN));
    }

    // UserGroupMapper 对应的 LambdaWrapper

    public LambdaQueryChainWrapper<UserGroup> refQueryChain() {
        return ChainWrappers.lambdaQueryChain(userGroupMapper);
    }

    public LambdaUpdateChainWrapper<UserGroup> refUpdateChain() {
        return ChainWrappers.lambdaUpdateChain(userGroupMapper);
    }

}